package com.lyloou.mvcframework.servlet;

import com.lyloou.mvcframework.annotation.*;
import com.lyloou.mvcframework.handler.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;

/**
 * @author lyloou
 * @date 2020/06/13
 * @desc
 */
public class MyDispatcherServlet extends HttpServlet {

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 加载配置
        doLoadConfig(config);

        // 扫描
        doScan(properties.getProperty("scanPackage"));

        // 初始化容器
        doInitInstance();

        // 依赖注入
        doAutowired();

        // 构造 handlerMapping
        doHandlerMapping();

        // 等待请求进入
        System.out.println("完成了初始化。。");
    }

    private Map<String, Object> iocMap = new HashMap<>();
    private List<Handler> handlerList = new ArrayList<>();

    private void doInitInstance() {
        if (classNameList.isEmpty()) {
            return;
        }

        try {
            for (String className : classNameList) {
                Class<?> clazz = Class.forName(className);
                if (clazz.isInterface()) {
                    continue;
                }

                if (clazz.isAnnotationPresent(MyController.class)) {
                    String name = lowerFirst(clazz.getSimpleName());
                    Object o = clazz.newInstance();
                    iocMap.put(name, o);
                    // 默认就行
                } else if (clazz.isAnnotationPresent(MyService.class)) {
                    String name = lowerFirst(clazz.getSimpleName());

                    MyService service = clazz.getAnnotation(MyService.class);
                    String value = service.value();
                    if (!"".equals(value.trim())) {
                        name = value;
                    }
                    Object o = clazz.newInstance();
                    iocMap.put(name, o);
                }

                // 根据接口的方式，保存一份，方便后面通过接口的方式获取
                Class<?>[] interfaces = clazz.getInterfaces();
                for (Class<?> iClazz : interfaces) {
                    iocMap.put(iClazz.getName(), clazz.newInstance());
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String lowerFirst(String simpleName) {
        char[] chars = simpleName.toCharArray();
        if (chars.length == 0) {
            return simpleName;
        }
        if (chars[0] >= 'A' && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }

    private void doHandlerMapping() {
        if (iocMap.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            Class<?> entryClass = entry.getValue().getClass();
            if (!entryClass.isAnnotationPresent(MyController.class)) {
                continue;
            }
            String baseUrl = "";
            if (entryClass.isAnnotationPresent(MyRequestMapping.class)) {
                baseUrl = entryClass.getAnnotation(MyRequestMapping.class).value();
            }

            Method[] methods = entryClass.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                    continue;
                }
                String methodUrl = method.getAnnotation(MyRequestMapping.class).value();
                String url = baseUrl + methodUrl;

                Handler handler = new Handler(entry.getValue(), method, Pattern.compile(url));
                Parameter[] parameters = method.getParameters();
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                        handler.getArgMap().put(parameter.getType().getSimpleName(), i);
                    } else {
                        handler.getArgMap().put(parameter.getName(), i);
                    }
                }
                handlerList.add(handler);

            }
        }
    }

    private void doAutowired() {
        if (iocMap.isEmpty()) {
            return;
        }
        try {
            for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
                Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
                for (Field field : declaredFields) {
                    if (field.isAnnotationPresent(MyAutowired.class)) {
                        MyAutowired myAutowired = field.getAnnotation(MyAutowired.class);
                        String beanName = myAutowired.value();
                        if ("".equals(beanName.trim())) {
                            beanName = lowerFirst(field.getType().getSimpleName());
                        }
                        field.setAccessible(true);
                        field.set(entry.getValue(), iocMap.get(beanName));
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private List<String> classNameList = new ArrayList<>();

    private void doScan(String scanPackage) {
        String pkg = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File file = new File(pkg);
        File[] files = file.listFiles();
        for (File f : files) {
            if (f.isDirectory()) {
                doScan(scanPackage + "." + f.getName());
            } else if (f.getName().endsWith(".class")) {
                String name = scanPackage + "." + f.getName().replaceAll(".class", "");
                classNameList.add(name);
            }
        }
    }

    private Properties properties = new Properties();

    private void doLoadConfig(ServletConfig config) {
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        InputStream is = getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Handler handler = getHandler(req);

        if (handler == null) {
            resp.getWriter().write("404 not found");
            return;
        }

        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        Object[] paramValues = new Object[parameterTypes.length];

        Map<String, String[]> reqParameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> param : reqParameterMap.entrySet()) {
            String value = StringUtils.join(param.getValue(), ",");
            if (!handler.getArgMap().containsKey(param.getKey())) {
                continue;
            }
            Integer index = handler.getArgMap().get(param.getKey());
            paramValues[index] = value;
        }

        Integer requestIndex = handler.getArgMap().get(HttpServletRequest.class.getSimpleName()); // 0
        if (requestIndex != null) {
            paramValues[requestIndex] = req;
        }


        Integer responseIndex = handler.getArgMap().get(HttpServletResponse.class.getSimpleName()); // 1
        if (responseIndex != null) {
            paramValues[responseIndex] = resp;
        }

        // 权限校验
        if (!checkSecurity(handler, paramValues)) {
            resp.getWriter().write("no permission");
            return;
        }

        try {
            handler.getMethod().invoke(handler.getController(), paramValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    private boolean checkSecurity(Handler handler, Object[] paramValues) {
        Map<String, Integer> argMap = handler.getArgMap();
        Integer index = argMap.getOrDefault("username", null);
        // 没有此字段，属于没权限
        if (index == null) {
            return false;
        }

        // 没有传入时，没权限
        String username = (String) paramValues[index];
        if (username == null) {
            return false;
        }

        if (handler.getMethod().isAnnotationPresent(Security.class)) {
            Security annotation = handler.getMethod().getAnnotation(Security.class);
            String[] value = annotation.value();
            for (String s : value) {
                if (username.equals(s)) {
                    return true;
                }
            }

        }
        return false;
    }

    private Handler getHandler(HttpServletRequest req) {
        if (handlerList.isEmpty()) {
            return null;
        }

        String requestURI = req.getRequestURI();

        for (Handler handler : handlerList) {
            if (handler.getPattern().matcher(requestURI).matches()) {
                return handler;
            }
        }
        return null;
    }
}
